package com.tesla.benjamin.charging.wallconnector.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.impl.JWTParser;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Payload;
import com.tesla.benjamin.charging.wallconnector.entity.JTWDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;

public class WebInterceptor implements HandlerInterceptor {

    @Value("${oauth2.public_key}")
    private String publicKey;

    public static final String KEY_ALGORITHM = "RSA";

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("Authorization");// 从 http 请求头中取出 token

        if (null == token || !token.startsWith("Bearer ")) {
            httpServletResponse.sendError(401, "No token found");
            return false;
        }

        try {
            boolean validated = validateToken(token, "service-account-vendor-xm");
            if (!validated) {
                throw new Exception();
            }
        } catch (Exception e) {
            httpServletResponse.sendError(500, "No token found");
        }
        return true;
    }

    /**
     * @param token client's token
     * @param serviceAccountName service account name
     * @return pass validated
     * @throws Exception
     */
    public boolean validateToken(String token, String serviceAccountName) throws Exception {
        JTWDTO jwtdto = new JTWDTO();
        jwtdto.setAlg("rs256");
        jwtdto.setToken(token.replace("Bearer ", ""));
        Algorithm algorithm = null;
        DecodedJWT verify = null;
        if ("rs256".equalsIgnoreCase(jwtdto.getAlg())) {
            algorithm = Algorithm.RSA256(genPk(), null);
        } else {
            algorithm = Algorithm.HMAC256(token);
        }
        JWTVerifier verifier = JWT.require(algorithm)
                .build();
        try {
            verify = verifier.verify(jwtdto.getToken());
            Map<String, Claim> claims = verify.getClaims();
            Claim preferred_username = claims.get("preferred_username");
            String accountName = preferred_username.asString();
            return accountName.equals(serviceAccountName);
        } catch (JWTVerificationException ex) {
            return false;
        }
    }


    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }

    private RSAPublicKey genPk() throws Exception {
        String key = publicKey.replace("-----BEGIN PUBLIC KEY-----", "").replace("\t", "")
                .replace("\n", "").replace("-----END PUBLIC KEY-----", "")
                .trim();
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);

        RSAPublicKey pk = getPublicKey(key);
        return pk;
    }

    public static RSAPublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (Base64.getDecoder().decode(key));
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return (RSAPublicKey) publicKey;
    }
}